<?php
// +-------------------------------------------------------------------------------
// | DingZone [ A Rapid Development Framework For Dingtalk ]
// +-------------------------------------------------------------------------------
// | AddonsCronTask.php v1.0
// |
// | Copyright (c) 2014-2018 http://www.heimalanshi.com All rights reserved.
// | Author : Jie
// | Date : 2018-10-01
// +-------------------------------------------------------------------------------
namespace addons\common\task;


use app\core\model\CronTask;
use app\core\model\CronTaskLog;
use app\core\model\EnterpriseModule;
use app\core\model\SiteModule;
use common\Http;
use ding\ApiHelper;
use ding\msg\robot\TextRobotMsg;
use think\Db;
use think\Env;
use think\Exception;
use think\Log;

abstract class AddonsCronTask {
    const ROBOT_KEY = "3255d17e2f5ce4a09a84975b31932363e52ecb2f67128e5f1b4e4a06d68e77b9";

    const MAX_LOG_LENGTH = 50000;

    const CODE_NOT_FOUND = -1;
    const CODE_NOT_ACTIVE = -2;
    const CODE_RUNNING_NOT_OVERLAP = -3;
    const CODE_EXCEPTION = -9999;
    const CODE_SUCCESS = 1;

    protected $cronTask;
    protected $module;
    protected $eids;
    protected $logs = [];

    /**
     * AddonsCronTask constructor.
     * @param $cronTask
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function __construct($cronTask) {
        $this->cronTask = $cronTask;

        $this->module = SiteModule::getByName($this->cronTask['module']);
        $this->eids = EnterpriseModule::getAllEidByMid(fetch($this->module, 'id'));
    }

    /**
     * 运行任务
     *
     * @throws Exception
     * @throws \think\exception\DbException
     * @throws \think\exception\PDOException
     */
    public function run() {
        $start = self::getMillisecond();
        $successCode = self::CODE_SUCCESS;
        try {
            if (empty($this->cronTask) || $this->cronTask['state'] != CronTask::NORMAL) {
                $this->addLog("未找到该任务或者该任务未激活");
                $successCode = self::CODE_NOT_ACTIVE;
                return [
                    "code" => $successCode,
                    "msg" => "未找到该任务或者该任务未激活"
                ];
            }

            if ($this->cronTask['overlap'] && CronTask::isTaskRunning($this->cronTask['module'], $this->cronTask['name'])) {
                $this->addLog("任务正在运行中，且不可重入，本次任务将不会执行");
                $successCode = self::CODE_RUNNING_NOT_OVERLAP;
                return [
                    "code" => $successCode,
                    "msg" => "任务正在运行中，且不可重入，本次任务将不会执行"
                ];
            }

            $successMsg = "";
            CronTask::taskRunning($this->cronTask['module'], $this->cronTask['name']);
            try {
                $this->execute();
                $end = self::getMillisecond();
                self::sendDingAlert("任务运行完毕：module=" . $this->cronTask['module'] . ", name=" . $this->cronTask['name'] . ", escape=" . ($end - $start) . "ms");
            } catch (\Exception $e) {
                $errorMsg = "任务运行异常：" . $e->getMessage() . "\n" . $e->getTraceAsString();
                $this->addLog($errorMsg);
                $successCode = self::CODE_EXCEPTION;
                $successMsg = "任务运行异常：" . $e->getMessage();
                self::sendDingAlert($errorMsg);
            }
            CronTask::taskFinish($this->cronTask['module'], $this->cronTask['name']);

            return [
                "code" => $successCode,
                "msg" => $successMsg
            ];
        } finally {
            if ($this->cronTask) {
                $log = join("\n", $this->logs);
                if (strlen($log) > self::MAX_LOG_LENGTH) {
                    $log = substr($log, 0, self::MAX_LOG_LENGTH);
                }
                $end = self::getMillisecond();
                CronTaskLog::add($this->cronTask['id'], gethostname(), Env::get('ops_server', 'unknown'),
                    round($start / 1000), round($end / 1000), $end - $start,
                    $successCode == self::CODE_SUCCESS ? 1 : 0, $log);
            }
        }
    }

    public static function getMillisecond() {
        list($t1, $t2) = explode(' ', microtime());
        return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
    }

    private static function _getRobotKey() {
        return Env::get("robot_key", self::ROBOT_KEY);
    }

    /**
     * 发送钉钉报警
     *
     * @param $msg
     */
    public static function sendDingAlert($msg) {
        $msg = "[" . gethostname() . "][" . Env::get('ops_server', 'unknown') . "]" . $msg;
        $robotMsg = new TextRobotMsg($msg);
        $url = ApiHelper::OAPI_URL . "robot/send?access_token=" . self::_getRobotKey();
        Http::postJson($url, json_encode($robotMsg->toMsg()));
    }

    /**
     * 执行任务
     *
     * @return mixed
     */
    public abstract function execute();

    /**
     * 续命（标识任务还在运行中）
     */
    public function keepAlive() {
        CronTask::taskRunning($this->cronTask['module'], $this->cronTask['name']);
        Log::save();
    }

    /**
     * 添加日志
     *
     * @param $msg
     */
    public function addLog($msg) {
        CronTask::addLog($this->cronTask, $msg);
        $this->logs[] = date("Y-m-d H:i:s", time()) . " " . $msg;
    }

    /**
     * 获取查询对象
     *
     * @param $name
     * @return \think\db\Query
     */
    protected function _q($name) {
        return Db::name($this->cronTask['module'] . "_" . $name);
    }
}